Skip to content

feat(namesys): propagate DNSLink TXT TTL - #1167

Merged
lidel merged 10 commits into
mainfrom
feat/dnslink-ttl
Jul 17, 2026
Merged

feat(namesys): propagate DNSLink TXT TTL#1167
lidel merged 10 commits into
mainfrom
feat/dnslink-ttl

Conversation

@lidel

@lidel lidel commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Problem

boxo's DNS resolver discards the TTL of DNSLink TXT records and returns 0. The gateway sets Cache-Control: max-age for mutable paths only when the TTL is non-zero, so DNSLink (/ipns/<dnslink-host>) responses go out with no max-age and fall back to a static value, even though the TXT record states exactly how long the mapping is valid (#329). Recursive names made it worse: a name resolved through several records was cached for only its final hop's TTL, ignoring shorter-lived links earlier in the chain.

Fix

  • Carry the TXT record TTL into the resolved result (AsyncResult.TTL / Result.TTL), which the gateway turns into Cache-Control: max-age.
  • WithDNSResolver auto-detects a resolver that implements madns.TXTWithTTLResolver; NewDNSResolverWithTTL, WithDNSResolverWithTTL, and LookupTXTWithTTLFunc take a TTL-aware lookup directly. Shipped end to end: go-multiaddr-dns v0.6.0 defines the interface, go-doh-resolver v0.6.0 implements it, both are pinned here, and a compile-time assertion in gateway makes a future drift break the build instead of silently dropping TTLs.
  • Cap a resolved name's TTL to its shortest hop, so a DNSLink pointing at an IPNS name, or a recursive IPNS chain, is not cached past its earliest-expiring link. Hops with an unknown TTL (0) are ignored.
  • WithMaxCacheTTL now also caps the TTL reported in results (making Kubo's documented Ipns.MaxCacheTTL behavior true in emitted headers), and cache hits report the entry's remaining lifetime instead of its original TTL, so a late hit no longer restarts the full caching period. A cap of 0 still means "no local cache" and leaves the TTL alone (Kubo's offline mode relies on that); the gateway sharness workflow now also watches namesys/ipns/path so a regression there cannot slip through untested again.

NewDNSResolver and LookupTXTFunc keep their signatures and report an unknown TTL (0). TTLs flow only for domains resolved through DoH1; everything else keeps today's static behavior. Independent of DoH, two changes apply to all namesys users: the WithMaxCacheTTL cap and remaining-lifetime cache hits.

Part of #329. ETag for resolved paths (the issue's other half) is out of scope.

Footnotes

  1. The OS resolver cannot report TTLs: Go's net.Resolver returns only record values, and madns.DefaultResolver wraps it. In Kubo, a DNS.Resolvers entry for . routes every lookup through DoH.

@lidel
lidel requested a review from a team as a code owner June 3, 2026 16:56
@lidel lidel added the status/blocked Unable to be worked further until needs are met label Jun 3, 2026
@lidel
lidel marked this pull request as draft June 3, 2026 17:01
The DNS resolver dropped the TXT record TTL, so a gateway could not set
Cache-Control max-age for DNSLink (/ipns/<dnslink-host>) responses, and a
recursive name was cached for only its final hop's TTL.

- add LookupTXTWithTTLFunc and NewDNSResolverWithTTL; LookupTXTFunc and
  NewDNSResolver keep their signatures and report an unknown TTL (0)
- carry the looked-up TTL into AsyncResult.TTL in the DNS resolver
- WithDNSResolver detects a resolver implementing
  multiformats/go-multiaddr-dns#75 TXTWithTTLResolver and propagates the
  TTL; add WithDNSResolverWithTTL for a TTL-aware lookup
- cap a resolved name's TTL to its shortest hop, so a DNSLink or
  recursive IPNS name is not cached past its earliest-expiring link

Refs #329
@lidel
lidel force-pushed the feat/dnslink-ttl branch from b2590e2 to fe47cf2 Compare June 3, 2026 17:09
@lidel
lidel marked this pull request as ready for review June 3, 2026 17:10
@codecov

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.84848% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.83%. Comparing base (441032d) to head (ad14954).

Files with missing lines Patch % Lines
namesys/namesys.go 44.44% 4 Missing and 1 partial ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1167      +/-   ##
==========================================
- Coverage   63.87%   63.83%   -0.04%     
==========================================
  Files         269      269              
  Lines       27073    27097      +24     
==========================================
+ Hits        17293    17298       +5     
- Misses       8072     8083      +11     
- Partials     1708     1716       +8     
Files with missing lines Coverage Δ
gateway/dns.go 67.56% <ø> (ø)
namesys/dns_resolver.go 73.68% <100.00%> (+1.46%) ⬆️
namesys/namesys_cache.go 62.50% <100.00%> (+6.94%) ⬆️
namesys/utilities.go 91.66% <100.00%> (+0.87%) ⬆️
namesys/namesys.go 57.89% <44.44%> (-2.11%) ⬇️

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread namesys/utilities.go Outdated
drop the min from negative inputs so a negative TTL is never
returned; cover the negative cases in the test table.
lidel added 7 commits July 17, 2026 12:02
WithMaxCacheTTL capped only how long an entry stayed in the resolution
cache; the TTL emitted in results (which a gateway turns into
Cache-Control max-age) was not capped, and cache hits reported the
entry's original TTL for the whole cache window, letting clients hold
a result up to twice the intended time.

- cap the TTL of fresh results with the same maxCacheTTL bound
- report a cache hit's remaining lifetime instead of its original TTL
- clamp a negative cap to 0 so it is never emitted
# Conflicts:
#	CHANGELOG.md
#	namesys/namesys_cache.go
#	namesys/namesys_test.go
namesys detects the TTL capability only at runtime, so a signature
drift in madns or go-doh-resolver would silently drop DNSLink
Cache-Control max-age. The assertion turns that into a compile error.
kubo's offline node passes WithMaxCacheTTL(0) to mean "no cache";
capping the reported TTL to that value dropped Cache-Control max-age
for IPNS responses on offline gateways (caught by kubo's
TestGateway/IPNS via the Gateway Sharness workflow). A non-positive
cap now only disables retention, while a positive cap still bounds
both the cache window and the reported TTL.
Gateway behavior is shaped by code outside gateway/: resolution TTLs
from namesys become Cache-Control max-age, ipns governs record TTL
and validation, and path parses gateway input. A namesys-only change
broke kubo's TestGateway/IPNS without triggering this workflow until
an unrelated gateway/ commit ran it.
Replaces the pseudo-version pins of the upstream PR branches with the
tagged releases. DoH resolvers now report TXT TTLs, which activates
DNSLink Cache-Control max-age end to end via WithDNSResolver's
auto-detection.
Go's net.Resolver returns only record values, so the OS resolver
cannot report TTLs and domains it serves keep a static Cache-Control.
Spell that out in WithDNSResolver and gateway.NewDNSResolver godocs,
with the catch-all "." DoH entry as the way to cover every domain.
@lidel
lidel merged commit 5a500f6 into main Jul 17, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status/blocked Unable to be worked further until needs are met

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants